package de.hft.st.algodat.ctt.algo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import de.hft.st.algodat.ctt.model.ConstraintPeriod;
import de.hft.st.algodat.ctt.model.ConstraintRoom;
import de.hft.st.algodat.ctt.model.Course;
import de.hft.st.algodat.ctt.model.Curriculum;
import de.hft.st.algodat.ctt.model.Problem;
import de.hft.st.algodat.ctt.model.Solution;
import de.hft.st.algodat.ctt.model.Timeslot;

/**
 * Reactive repetitive reconstruction
 * 
 * This approach to find a feasible solutions follows closely Martin Josef
 * Geiger's paper "An application of the Threshold Accepting metaheuristic for
 * curriculum based course timetabling"
 * 
 */

public class ReactiveConstruction {
	private final int MAX_ITERATIONS = 3;
	private Problem pRef;
	private Solution sRef;
	private int[] cPri;
	private int[] tsPri;

	AllEvents allEvents;
	PrioritizedEvents prioritizedEvents;
	NonPrioritizedEvents nonPrioritizedEvents;
	NotAssignedEvents notAssignedEvents;
	AssignedEvents assignedEvents;

	public ReactiveConstruction(Problem theProblem, Solution theSolution,
			int[] coursesPri, int[] timeslotsPri) {
		this.pRef = theProblem;
		this.sRef = theSolution;
		this.cPri = coursesPri;
		this.tsPri = timeslotsPri;
	}

	public void constructFeasibleSolution() {
		// e - set of all events
		// eP - set of prioritized events
		// eNotP - set of non prioritized events
		// eU - set of during construction phase not
		// assigned events
		initConstruction();
		int loops = 0;
		do {
			System.out.println("not assigned events size: "
					+ notAssignedEvents.getEvents().size());
			prioritizedEvents.setEvents(notAssignedEvents.getEvents());
			notAssignedEvents.emptyEventsList();
			nonPrioritizedEvents.setEvents(removeEvents(removeEvents(allEvents
					.getEvents(), prioritizedEvents.getEvents()),
					assignedEvents.getEvents()));

			System.out.println("removed "
					+ prioritizedEvents.getEvents().size() + " and " +
					  assignedEvents.getEvents().size() + " from "
					+ allEvents.getEvents().size() + " resulting in "
					+ nonPrioritizedEvents.getEvents().size());
			System.out.println("not assigned events size is now: "
					+ notAssignedEvents.getEvents().size());

			while (prioritizedEvents.getEvents().size() > 0) {
				Event mostCritical = prioritizedEvents.getMostCriticalEvent();
				List<Timeslot> workerSlots = mostCritical
						.getAvailableTimeslots();
				if (workerSlots.size() > 0) {
					getIndexOfHighestPrioTS(workerSlots).setAssigned(
							mostCritical.getCourse());
					assignedEvents.addEvent(mostCritical);
				} else {
					notAssignedEvents.addEvent(mostCritical);
				}
				prioritizedEvents.removeEvent(mostCritical);
			}
			System.out.println("not assigned after while 1 "
					+ notAssignedEvents.getEvents().size());
			while (nonPrioritizedEvents.getEvents().size() > 0) {
				Event mostCritical = nonPrioritizedEvents
						.getMostCriticalEvent();
				List<Timeslot> workerSlots = mostCritical
						.getAvailableTimeslots();
				if (workerSlots.size() > 0) {
					Timeslot dts = getIndexOfHighestPrioTS(workerSlots);
					dts.setAssigned(mostCritical.getCourse());
					assignedEvents.addEvent(mostCritical);
					// sRef.setAssignedCurriculaWithSameCourse(pRef
					// .getCurriculaWithSameCourse(mostCritical
					// .getCourse().getCID(), mostCritical
					// .getCourse().getId()), mostCritical
					// .getCourse(),dts);
				} else {
					notAssignedEvents.addEvent(mostCritical);
				}
				nonPrioritizedEvents.removeEvent(mostCritical);
			}
			System.out.println("not assigned after while 2 "
					+ notAssignedEvents.getEvents().size());
			loops++;
			System.out.println("LOOPS: " + loops);
			System.out.println("Number of unassigned: "
					+ notAssignedEvents.getEvents().size());
		} while ((notAssignedEvents.getEvents().size() > 0)
				&& (loops < MAX_ITERATIONS));
	}

	private Timeslot getIndexOfHighestPrioTS(List<Timeslot> workerSlots) {
		int highVal = -1;
		Timeslot ts = null;
		for (Iterator<Timeslot> itr = workerSlots.iterator(); itr.hasNext();) {
			Timeslot worker = itr.next();
			if (tsPri[worker.getTsID()] > highVal) {
				highVal = tsPri[worker.getTsID()];
				ts = worker;
			}
		}
		return ts;
	}

	private void initConstruction() {
		allEvents = new AllEvents();
		prioritizedEvents = new PrioritizedEvents();
		nonPrioritizedEvents = new NonPrioritizedEvents();
		notAssignedEvents = new NotAssignedEvents();
		assignedEvents = new AssignedEvents();
	}

	private List<Event> removeEvents(List<Event> from, List<Event> removes) {
		List<Event> resultList = new ArrayList<Event>();
		for (Iterator<Event> itr = from.iterator(); itr.hasNext();) {
			Event worker = itr.next();
			if (!removes.contains(worker)) {
				resultList.add(worker);
			}
		}
		return resultList;
	}

	private class NotAssignedEvents extends Events {
	}

	private class NonPrioritizedEvents extends Events {
		protected Event getMostCriticalEvent() {
			int highVal = -1;
			Event mCrit = null;
			for (Iterator<Event> itr = this.events.iterator(); itr.hasNext();) {
				Event worker = itr.next();
				if (cPri[worker.getCPriID()] > highVal) {
					highVal = cPri[worker.getCPriID()];
					mCrit = worker;
				}
			}
			return mCrit;
		}
	}

	private class PrioritizedEvents extends Events {
		protected Event getMostCriticalEvent() {
			int highVal = -1;
			Event mCrit = null;
			for (Iterator<Event> itr = this.events.iterator(); itr.hasNext();) {
				Event worker = itr.next();
				if (cPri[worker.getCPriID()] > highVal) {
					highVal = cPri[worker.getCPriID()];
					mCrit = worker;
				}
			}
			return mCrit;
		}
	}

	private class AllEvents extends Events {
		private AllEvents() {
			int cPriID = 0;
			for (Iterator<Curriculum> cuItr = pRef.getCurricula().iterator(); cuItr
					.hasNext();) {
				for (Iterator<Course> coItr = cuItr.next().getCList()
						.iterator(); coItr.hasNext();) {
					events.add(new Event(coItr.next(), cPriID));
					cPriID++;
				}
			}
		}
	}

	private class AssignedEvents extends Events {
		private AssignedEvents() {
		}
	}

	private int getIndexOfHighestValue(int[] searcher) {
		int index = 0;
		int value = searcher[0];
		for (int i = 1; i < searcher.length; i++) {
			if (searcher[i] > value) {
				index = i;
				value = searcher[i];
			}
		}
		return index;
	}

	protected class Event {
		private Course course;
		private int cPriID;

		private Event(Course course, int cPriID) {
			this.course = course;
			this.cPriID = cPriID;
		}

		private List<Timeslot> getAvailableTimeslots() {
			String currID = this.course.getCID();
			Curriculum curriculumWorker = pRef.getCurriculumByID(currID);
			List<Curriculum> currWithSameCourse = pRef
					.getCurriculaWithSameCourse(currID, this.course.getId());
			List<Timeslot> availableTS = new ArrayList<Timeslot>();
			for (Iterator<Timeslot> itr = sRef.getTimeSlots().iterator(); itr
					.hasNext();) {
				Timeslot ts = itr.next();
				if (!ts.isAssigned()) {
					if (validateTS(curriculumWorker, ts)) {
						boolean valid = true;
						for (Iterator<Curriculum> cItr = currWithSameCourse
								.iterator(); cItr.hasNext();) {
							Curriculum curr = cItr.next();
							if (!validateTS(curr, ts)) {
								valid = false;
								break;
							}
						}
						if (valid) {
							availableTS.add(ts);
						}
					}
				}

			}

			// check whether curriculum has another lecture around (done)
			// check whether teacher has another lecture around (done)
			// check room constraints (done?)
			// check period constraint (done?)

			return availableTS;
		}

		private boolean validateTS(Curriculum curriculumWorker, Timeslot ts) {
			boolean valid = true;
			List<Timeslot> workerList = sRef.getAssignedTimeSlotsByTimeslot(ts);
			for (Iterator<Timeslot> dItr = workerList.iterator(); dItr
					.hasNext();) {
				Timeslot tsW = dItr.next();
				if (curriculumWorker.hasCourse(tsW.getAssigned())) {
					valid = false;
					break;
				}
				if (ts.getAssigned() != null
						&& tsW.getAssigned().getTeacher().equals(
								ts.getAssigned().getTeacher())) {
					valid = false;
					break;
				}
				List<ConstraintPeriod> cPeriods = pRef.getConstraintsPeriod();
				for (Iterator<ConstraintPeriod> cPItr = cPeriods.iterator(); cPItr
						.hasNext();) {
					ConstraintPeriod cPWorker = cPItr.next();
					List<Timeslot> cPTsWorker = cPWorker
							.getForbiddenTimeslots();
					for (Iterator<Timeslot> cPTsItr = cPTsWorker.iterator(); cPTsItr
							.hasNext();) {
						Timeslot dummy = cPTsItr.next();
						if (dummy.getDay() == ts.getDay()
								&& dummy.getPeriod() == ts.getPeriod()) {
							valid = false;
							break;
						}
					}
				}
				List<ConstraintRoom> cRooms = pRef.getConstraintsRoom();
				for (Iterator<ConstraintRoom> cRItr = cRooms.iterator(); cRItr
						.hasNext();) {
					ConstraintRoom cRWorker = cRItr.next();
					if (!this.course.getId().equals(cRWorker.getCourseID())) {
						continue;
					}
					List<String> cRTsWorker = cRWorker.getForbiddenRooms();
					for (Iterator<String> cRRItr = cRTsWorker.iterator(); cRRItr
							.hasNext();) {
						String dummy = cRRItr.next();
						if (ts.getRoom().getId().equals(dummy)) {
							valid = false;
							break;
						}
					}
				}
			}
			return valid;
		}

		private int getCPriID() {
			return this.cPriID;
		}

		private Course getCourse() {
			return this.course;
		}
	}

	private class Events {
		List<Event> events = new ArrayList<Event>();

		protected void addEvent(Event event) {
			this.events.add(event);
		}

		protected void removeEvent(Event event) {
			this.events.remove(event);
		}

		protected void setEvents(List<Event> events) {
			this.events = events;
		}

		protected List<Event> getEvents() {
			return this.events;
		}

		protected void emptyEventsList() {
			this.events = new ArrayList<Event>();
		}
	}
}
